Skip to content
On this page

数据分析师的好帮手 · 辅助线


第 16 节 数据分析师的好帮手 · 辅助线

经过了一系列的学习,我们已经掌握了多种日常开发中最常用的数据图表,并且可以对各种结构的数据集进行预处理。但是在我们进行可视化图表开发的时候经常会发现如果仅仅将数据使用数据系列展示在图表上的话,是没办法非常直观地展示所有数据信息的。

而这个时候,辅助线便成了帮助开发人员和分析人员更好地利用可视化图表的强有力工具。

16.1 为什么要使用辅助线

我们在第 5 节中学习了如何利用 JavaScript 对数据中的一些数学特征值进行计算,而这些数学特征值往往可以更好、更直观地将数据的基本状况表达出来。

但是这些数学特征值往往只是通过对一组数值进行计算过后得到的另一组数值,那么辅助线便是帮助开发者和数据分析人员更好地使用这些数学特征值的最好工具。

16.2 辅助线基本操作

在 ECharts 中辅助线并不是一种独立的数据类型,它需要依附在某一个数据系列上以表示其与该数据系列的关系。

假设我们有以下数据集,并将其绘制成一个简单的柱状图。

const chartEl = document.querySelector('#chart')
const myChart = echarts.init(chartEl)

const data = [ 50, 61, 56, 46, 72, 53 ]

const option = {
  dataset: {
    source: data.map((y, i) => ({
      x: i + 1,
      y
    }))
  },

  xAxis: {
    type: 'category'
  },
  yAxis: {
    type: 'value'
  },
  
  series: {
    type: 'bar',
    encode: {
      x: 'x',
      y: 'y'
    }
  }
}

然后接下来我们通过非常简单的计算,得出这一组数据的平均数。

const mean = data.reduce((left, right) => left + right) / data.length
console.log(mean) //=> 56.333333333333336

假如说需要将这个计算结果展示在图表上,那么根据目前所设定的坐标系可知我们需要添加一条横向的水平线,而这条水平线的纵向位置应该为 y 坐标轴上该数值所对应的位置。

那么在 ECharts 中便需要在对应的数据系列上添加一个 markLine 配置,并在 markLine.data 中添加一个 yAxis 值为对应平均值的配置。

const option = {
  // ...
  
  series: {
    // ...
    
    markLine: {
      data: [
        {
          name: '平均线',
          yAxis: mean
        }
      ]
    }
  }
}

16.2.1 ECharts 的自带辅助线

除了我们可以自行计算目标辅助线的数值以外,ECharts 自身也提供了一些比较常用的辅助线,除了前面我们自行计算的平均值外,还有最大值和最小值。

const option = {

  // ...
  
  series: {
    // ...
    
    markLine: {
      data: [
        { name: '平均值', type: 'average' },
        { name: '最大值', type: 'max' },
        { name: '最小值', type: 'min' }
      ]
    }
  },

}

16.3 辅助线高级用法

是否觉得前面的辅助线都太简单而没有挑战性了?恭喜你已经拥有了成为大牛的一个非常重要的优秀特点,那么我们接下来便需要向更复杂、更具有功能性的辅助线应用进发吧。

16.3.1 SPC 控制图

在传统的统计学领域中,有一种广泛用于工业生产的统计方法——质量管理。在工业生产领域中,企业为了能够稳定且长期地发展产品的质量和销量,必须要对产品生产过程中的各种数据进行监控和分析,比如生产原料、成本、产品特性、质量指标、销量等等。

而其中成本和质量指标直接关系到了企业的长期生存条件,所以对这些数据的监控和分析则显得尤为重要。其中有一种名为 SPC 控制图的数据可视化图表的应用非常广泛,它通过对数据进行计算并将计算结果作为辅助线绘制在图表上。这些辅助线可以帮助数据分析人员非常直观地看到数据中的总体状况和突发的异常情况等。

SPC 控制图事实上是多种控制图表的总称,但其核心都是相似的。SPC 控制图主要通过计算三个控制线:UCL(控制上限)、CL(中心线)和 LCL(控制下限)。在一些情况下还可以将控制图的上下限的中间区域分为 6 等份,并分别标记为控制 A 区、B 区以及 C 区,并通过记录数据点落在这三个控制区域的数量来对数据的稳定性进行直观的判定。

注:图片来源 Wikipedia —— Western Electric rules

16.3.2 建立数据集

假设我们通过随机方法生成一组数值数据(参考第 11 节),并将其绘制到折线图上。

const X = [ 100 ]
const n = 50 - 1
const r = 0.1

function randomCoefficient(r) {
  const rand = Math.random()
  const coefficient = (rand - 0.5) * 2 * r

  return coefficient
}

for (let i = 0; i < n; ++i) {
  const coefficient = randomCoefficient(r)
  const newValue = X[i] * (1 + coefficient)

  X.push(newValue)
}

console.log(X) //=> [ 100, 95.23, ... ]

const data = X.map(function(x, i) {
  return { time: i + 1, value: x }
})

const option = {
  dataset: {
    source: data
  },
  xAxis: {
    type: 'value',
    name: 'i',
    nameLocation: 'middle',
    nameGap: 25
  },
  yAxis: {
    type: 'value',
    scale: true,
    name: 'x',
    nameLocation: 'end'
  },
  series: {
    type: 'line',
    encode: {
      x: 'time',
      y: 'value'
    }
  },
}

16.3.3 计算 SPC 控制图的必要数值

SPC 控制图所使用的数据主要需要计算数据的平均值和标准差(Standard deviation,并非标准误 Standard error)。平均值的计算我们使用 Lodash 中的 _.mean 即可,但 Lodash 并没有提供标准差 igma 的计算方法,所以我们这里也需要自行实现一下标准差的计算方法。N 为数组 x 的长度,verline{x} 为数组 x 的平均值。

egin{align*}
verline{x} &= rac{um^{N}{i=1} x_i}{N} 
igma &= qrt{rac{um^{N}{i=1}(x_i - verline{x})^2}{N - 1}}
nd{align*}

function sd(array) {
  const mean = _.mean(array)
  
  const top = array
    .map(function(x) {
      return Math.pow(x - mean, 2)
    })
    .reduce(function(left, right) {
      return left + right
    })
  const bottom = array.length - 1
  
  return Math.sqrt(top / bottom)
}

计算所得数据的平均值和标准差后,便可以计算 SPC 控制图中的 UCL 和 LCL 控制值了。UCL 和 LCL 的值分别为以下:

UCL = verline{x} + 3 imes igma 
LCL = verline{x} - 3 imes igma

其中从上面的图中我们可以看到,SPC 控制图可以将从 LCL 到 UCL 中间的区域等分为 6 份,显然可以得出控制区域的区间为以下:

egin{align*}
& A = eft
egin{array}{lr}
[verline{x} + 2 * igma, verline{x} + 3 * igma], &  
[verline{x} - 3 * igma, verline{x} - 2 * igma] &nd{array}
ight. 
& B = eft
egin{array}{lr}
[verline{x} + igma, verline{x} + 2 * igma], &  
[verline{x} - 2 * igma, verline{x} - igma] &nd{array}
ight. 
& C = [verline{x} - igma, verline{x} + igma]
nd{align*}

在 EChart 中,除了辅助线以外还提供了一个非常实用的工具 —— visualMap。它可以将图表中某一个区域内的元素统一为一种颜色,这正好可以应用到 SPC 控制图的三个控制区域上。

首先我们需要计算所需要的数据。

const mean_X = _.mean(X)
const sd_X = sd(X)

const ucl = mean_X + 3 * sd_X
const lcl = mean_X - 3 * sd_X

const areaA = [
  [ mean_X + 2 * sd_X, mean_X + 3 * sd_X ],
  [ mean_X - 3 * sd_X, mean_X - 2 * sd_X ]
]
const areaB = [
  [ mean_X + sd_X, mean_X + 2 * sd_X ],
  [ mean_X - 2 * sd_X, mean_X - sd_X ]
]
const areaC = [
  [ mean_X - sd_X, mean_X + sd_X ]
]

16.3.4 绘制 SPC 控制图

首先我们将控制线通过 markLine 组件绘制在图表上。

const option = {
  // ...
  
  yAxis: {
    type: 'value',
    name: 'x',
    nameLocation: 'end',
    
    max: Math.max(ucl + 5, Math.max(...X)),
    min: Math.min(lcl - 5, Math.min(...X))
  },
  
  series: {
    // ...
    
    markLine: {
      data: [
        { name: 'UCL', yAxis: ucl },
        { name: 'Area B', yAxis: areaB[0][1] },
        { name: 'Area C', yAxis: areaC[0][1] },
        { name: 'Mean', yAxis: mean_X },
        { name: 'Area C', yAxis: areaC[0][0] },
        { name: 'Area B', yAxis: areaB[1][0] },
        { name: 'LCL', yAxis: lcl }
      ]
    }
  }
}

然后结合 visualMap 我们便可以将完整的 SPC 控制图绘制出来了。

const option = {
  // ...
  
  visualMap: {
    top: 10, right: 10, // visualMap 图例位置
    pieces: [
      /* Area A */ { gt: areaA[0][0], lte: areaA[0][1], color: '#cc0033' },
      /* Area B */ { gt: areaB[0][0], lte: areaB[0][1], color: '#ffde33' },
      /* Area C */ { gt: areaC[0][0], lte: areaC[0][1], color: '#096' },
      /* Area B */ { gt: areaB[1][0], lte: areaB[1][1], color: '#ffde33' },
      /* Area A */ { gt: areaA[1][0], lte: areaA[1][1], color: '#cc0033' }
    ]
  }
}

小结

在我们进行数据分析的时候,如果只有独立的数据图表而没有加以辅助的工具,数据分析工作的效率就会大大降低。所以利用数学计算配合图形展示的方式为数据图表添加辅助线以及其他辅助工具(如标注区域等),可以为数据图表的使用者带来极大的便利性。